/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package replic;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import replic.catalog.Catalog;
import replic.catalog.DB;
import replic.commons.jdbc.Column;
import replic.commons.jdbc.Message;
import replic.commons.OutputMessage;
import replic.commons.jdbc.Row;

/**
 *
 * @author Leonardo Oliveira Moreira
 */
public class ClientProxy extends Thread {

    private RepliCService repliCService;
    private Socket dbConnection;
    private Catalog catalog;
    private Hashtable<String, List<DatabaseProxy>> databaseList;
    private Hashtable<Long, Statement> statementList;
    private Hashtable<Long, ResultSet> resultSetList;
    private long clientID;

    public ClientProxy(RepliCService repliCService, Socket dbConnection, Catalog catalog) {
        this.clientID = System.currentTimeMillis();
        this.repliCService = repliCService;
        this.dbConnection = dbConnection;
        this.catalog = catalog;
        databaseList = new Hashtable<String, List<DatabaseProxy>>();
        statementList = new Hashtable<Long, Statement>();
        resultSetList = new Hashtable<Long, ResultSet>();
        // lista com todas os bancos de dados do catalogo, inclusive replicas
        List<DB> dbList = catalog.getDatabaseList();
        // percorre todas os bancos de dados encontrados
        for (DB db : dbList) {
            // recuper a lista de replicas do banco de dados
            List<DatabaseProxy> databaseQueue = databaseList.get(db.getDbName());
            // caso nao exista nenhuma, crie a lista vazia
            if (databaseQueue == null) {
                databaseQueue = new ArrayList<DatabaseProxy>();
            }
            // adicione nova uma conexao com o banco de dados na fila de replicas deste banco de dados
            DatabaseProxy dp = new DatabaseProxy(
                    db.getDbDriver(),
                    db.getDbUrl(), db.getDbName(),
                    db.getDbUser(),
                    db.getDbPassword(), db.getVmName());
            databaseQueue.add(dp);
            // atualize a lista de banco de dados
            databaseList.put(db.getDbName(), databaseQueue);
        }
    }

    private DatabaseProxy getDatabaseProxy(String databaseName) {
        List<DatabaseProxy> databaseQueue = databaseList.get(databaseName);
        DatabaseProxy result = databaseQueue.remove(0);
        databaseQueue.add(result);
        return result;
    }

    private Statement getStatement(long statementId) {
        return statementList.get(statementId);
    }

    private ResultSet getResultSet(long resultSetId) {
        return resultSetList.get(resultSetId);
    }

    @Override
    public void run() {
        OutputMessage.println("[cp@" + clientID + "]: ClientProxy Starting");
        ObjectOutputStream outputStream = null;
        ObjectInputStream inputStream = null;
        boolean proceed = true;
        try {
            outputStream = new ObjectOutputStream(dbConnection.getOutputStream());
            inputStream = new ObjectInputStream(dbConnection.getInputStream());
        } catch (IOException ex) {
            OutputMessage.println("[cp@" + clientID + "]: Closing client connection");
            proceed = false;
        }
        OutputMessage.println("[cp@" + clientID + "]: ClientProxy Started");

        DatabaseProxy dao = null;
        boolean getNewDatabase = true;

        boolean closeConnection = false;
        while (proceed && dbConnection != null && dbConnection.isConnected()) {
            try {
                Object message = inputStream.readObject();
                synchronized (this) {
                    if (message instanceof Message) {
                        Message msg = (Message) message;

                        if (getNewDatabase) {
                            dao = getDatabaseProxy(msg.getDatabase());
                            getNewDatabase = false;
                        }

                        long startTime = System.currentTimeMillis();
                        long affectedLines = 0;

                        switch (msg.getSqlType()) {
                            case Message.SQL_CONNECTION_CREATE: {
                                msg.setState(Message.STATE_FAILURE);
                                dao = getDatabaseProxy(msg.getDatabase());
                                getNewDatabase = false;
                                msg.setState(Message.STATE_SUCCESS);
                                break;
                            }
                            case Message.SQL_CONNECTION_CLOSE: {
                                msg.setState(Message.STATE_FAILURE);
                                dao.rollback();
                                closeConnection = true;
                                msg.setState(Message.STATE_SUCCESS);
                                break;
                            }
                            case Message.SQL_STATEMENT_CREATE: {
                                msg.setState(Message.STATE_SUCCESS);
                                try {
                                    Statement statement = dao.getConnection().createStatement();
                                    statementList.put(msg.getStatementID(), statement);
                                } catch (SQLException ex) {
                                    msg.setState(Message.STATE_FAILURE);
                                }
                                break;
                            }
                            case Message.SQL_STATEMENT_CLOSE: {
                                msg.setState(Message.STATE_SUCCESS);
                                try {
                                    getStatement(msg.getStatementID()).close();
                                    statementList.remove(msg.getStatementID());
                                } catch (SQLException ex) {
                                    msg.setState(Message.STATE_FAILURE);
                                }
                                break;
                            }
                            case Message.SQL_RESULTSET_CREATE: {
                                int count = -1;
                                msg.setState(Message.STATE_FAILURE);
                                try {
                                    Statement statement = getStatement(msg.getStatementID());
                                    ResultSet resultSet = statement.executeQuery(msg.getSql());
                                    List<Row> resultSetList = transformResultSetToRowList(resultSet);
                                    if (resultSetList != null) {
                                        count = resultSetList.size();
                                    }
                                    msg.setResultSetList(resultSetList);
                                    this.resultSetList.put(msg.getResultSetID(), resultSet);
                                    msg.setState(Message.STATE_SUCCESS);
                                } catch (SQLException ex) {
                                    msg.setState(Message.STATE_FAILURE);
                                }
                                affectedLines = count;
                                break;
                            }
                            case Message.SQL_RESULTSET_CLOSE: {
                                msg.setState(Message.STATE_SUCCESS);
                                try {
                                    getResultSet(msg.getResultSetID()).close();
                                    resultSetList.remove(msg.getResultSetID());
                                } catch (SQLException ex) {
                                    msg.setState(Message.STATE_FAILURE);
                                }
                                break;
                            }
                            case Message.SQL_COMMIT: {
                                msg.setState(Message.STATE_FAILURE);
                                dao.commit();
                                msg.setState(Message.STATE_SUCCESS);
                                getNewDatabase = true;
                                break;
                            }
                            case Message.SQL_ROLLBACK: {
                                msg.setState(Message.STATE_FAILURE);
                                dao.rollback();
                                msg.setState(Message.STATE_SUCCESS);
                                getNewDatabase = true;
                                break;
                            }
                            case Message.SQL_UPDATE: {
                                msg.setState(Message.STATE_FAILURE);
                                int result = dao.update(msg.getSql(), getStatement(msg.getStatementID()));
                                affectedLines = result;
                                msg.setState(Message.STATE_SUCCESS);
                                break;
                            }
                        }

                        long finishTime = System.currentTimeMillis();

                        msg.setSqlAffectedLines(affectedLines);
                        msg.setSqlResponseTime(finishTime - startTime);
                        msg.setVmName(dao.getVirtualMachineName());

                        outputStream.writeObject(msg);
                        outputStream.flush();
                        
                        if (closeConnection) {
                            OutputMessage.println("[cp@" + clientID + "]: Closing client connection");
                            if (dbConnection != null) {
                                try {
                                    dbConnection.close();
                                } catch (IOException ex1) {
                                    dbConnection = null;
                                }
                            }
                            break;
                        }
                    } else {
                        throw new IOException("[cp@" + clientID + "]: Invalid message format");
                    }
                } // SYNCHRONIZED
            } catch (IOException ex) {
                OutputMessage.println("[cp@" + clientID + "]: Closing client connection");
                if (dbConnection != null) {
                    try {
                        dbConnection.close();
                    } catch (IOException ex1) {
                        dbConnection = null;
                    }
                }
                break;
            } catch (ClassNotFoundException ex) {
                OutputMessage.println("[cp@" + clientID + "]: Closing client connection");
                if (dbConnection != null) {
                    try {
                        dbConnection.close();
                    } catch (IOException ex1) {
                        dbConnection = null;
                    }
                }
                break;
            } // TRY
        } // WHILE
        // Close all database connections
        Enumeration<String> databaseNames = databaseList.keys();
        while (databaseNames.hasMoreElements()) {
            List<DatabaseProxy> databaseProxys = databaseList.get(databaseNames.nextElement());
            while (databaseProxys != null && databaseProxys.size() > 0) {
                DatabaseProxy dp = databaseProxys.remove(0);
                if (dp != null && dp.isActive()) {
                    dp.close();
                }
            }
        }
        databaseList.clear();
        OutputMessage.println("[cp@" + clientID + "]: ClientProxy Ended");
    }

    public static List<Row> transformResultSetToRowList(ResultSet resultSet) {
        List<Row> resultSetList = new ArrayList<Row>();
        try {
            java.sql.ResultSetMetaData metaData = resultSet.getMetaData();
            while (resultSet.next()) {
                Row row = new Row();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    Column column = new Column();
                    column.setColumnIndex(i);
                    column.setColumnLabel(metaData.getColumnLabel(i));
                    column.setColumnValue(resultSet.getObject(metaData.getColumnLabel(i)));
                    row.addColumn(column);
                }
                resultSetList.add(row);
            }
            return resultSetList;
        } catch (SQLException ex) {
        }
        return null;
    }
}